//
// Created on 2022/12/12.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

constexpr uint32_t DEFAULT_WIDTH = 240;
constexpr uint32_t DEFAULT_HEIGHT = 320;
constexpr uint32_t DEFAULT_PIXELFORMAT = AV_PIXEL_FORMAT_NX12;
constexpr uint32_t DEFAULT_FRAMERATE = 30;

std::atomic<bool> isVdecRunning_;
constexpr uint32_t SAMPLE_DURATION_US=23000;
const uint32_t ES[] ={
    
             

}
constexpr uint32_t ES_LENGTH=sizeof(ES) / sizeof(uint32_t);

OH_NativeXComponent_Callback VCodecNdkSample::callback_;

VCodecNdkSample VCodecNdkSample::mCodecNdkSample;

std::unique_ptr<std::ifstream> VCodecNdkSample::testFile_;
std::unique_ptr<std::thread> VCodecNdkSample::readLoop_;
std::unique_ptr<std::thread> VCodecNdkSample::vdecOutLoop_;

VDecSignal *VCodecNdkSample::signal_;
OH_AVCodec *_codec;

void *nativewindow_;

OH_AVCodecAsyncCallback _callback;

void OH_AVCodecOnErrorB(OH_AVCodec *codec,int32_t errorcode,void *userData){
    VDecSignal *vcodecSignal_=static_cast<VDecSignal *>(userData);
    vcodecSignal_->errorNum_+=1;
}

void OH_AVCodecOnStreamChangedB(OH_AVCodec *codec,OH_AVFormat *format,void *userData){
    
}

void OH_AVCodecOnNeedInputDataB(OH_AVCodec *codec,int32_t index,OH_AVMemory *data,void *userData){
      VDecSignal *vcodecSignal_=static_cast<VDecSignal *>(userData);
      std::unique_lock<std::mutex> lock(vcodecSignal_->vdecInpMutex_);
      if(vcodecSignal_->isVdecFlushing_.load()){
        return;
      }
     vcodecSignal_->inQueue_.push(index);
     vcodecSignal_->vdecInpBufferQueue_.push(data);
     vcodecSignal_->vdecIncond_.notify_all();
}

void  OH_AVCodecOnNeedOutputDataB(OH_AVCodec *codec,int32_t index,OH_AVMemory *data,OH_AVCodecBufferAttr *attr,void *userData){
      VDecSignal *vcodecSignal_=static_cast<VDecSignal *>(userData);
      std::unique_lock<std::mutex> lock(vcodecSignal_->vdecOutMutex_);
       if(vcodecSignal_->isVdecFlushing_.load()){
         return;
       }
     vcodecSignal_->outQueue_.push(index);
     vcodecSignal_->vdecOutFlagQueue_.push(attr->flags);
     vcodecSignal_->vdecOutCond_.notify_all();
}

void VCodecNdkSample::PopInqueueDec(){
    if(signal_==nullptr){
      return;
    }
    signal_->inQueue_.pop();
    signal_->vdecInpBufferQueue_.pop();
}

void VCodecNdkSample::PopOutqueueDec(){
    if(signal_==nullptr){
      return;
    }
    signal_->outQueue_.pop();
    signal_->vdecOutFlagQueue_.pop();
}

int32_t VCodecNdkSample::PushInbufferDec(uint32_t index,uint32_t bufferSize){
    if(_codec==nullptr){ 
        return AV_ERR_INVALID_VAL;
    }
    struct OH_AVCodecBufferAttr attr;
    attr.offset=0;
    if(decInCnt_==ES_LENGTH){
       attr.flags=AVCODEC_BUFFER_FLAGS_EOS;
       attr.pts=0;
        attr.size=0;
        isDecInputEOS=true;
    }
    else{
        attr.pts=timeStampDec_;
        attr.size=bufferSize;
        if(isFirstDecFrame_){
            attr.flags=AVCODEC_BUFFER_FLAGS_CODEC_DATA;
            isFirstDecFrame_=false;
        }
        else{
           attr.flags=AVCODEC_BUFFER_FLAGS_NONE;
        }
    }
    return OH_VideoDecoder_PushInputData(_codec,index,attr);
}


void VCodecNdkSample::VDecInLoopFunc(){
    while(true){
        if(!isVdecRunning_.load()){
            break;
        }
        std::unique_lock<std::mutex> lock(vcodecSignal_->vdecInpMutex_);
        signal_->vdecInCond_.wait(lock,[this](){
            return signal_->inQueue_.size()>0;
        });
        if(!isVdecRunning_.load()){
            break;
        }
        uint32_t index=signal_->inQueue_.front();
        OH_AVMemory *buffer = reinterpret_cast<OH_AVMemory *>(signal_->vdecInpBufferQueue_.front());
        if(signal_->isVdecFlushing_.load()||isDecInputEOS||buffer==nullptr){
          PopInqueueDec();
          continue;  
        }
        uint32_t bufferSize=0;
        if(decInCnt_<ES_LENGTH){
           bufferSize=ES[descInCnt_];
           char *fileBuffer=(char*)malloc(sizeof(char)*bufferSize+1);
           (void)testFile_->read(fileBuffer,bufferSize);
            if(testFile_->eof()){
              free(fileBuffer);
              break;  
            }
            memcpy(OH_AVMemory_GetAddr(buffer),fileBuffer,bufferSize);
            if(buffer == nullptr){
              free(fileBuffer);
              PopInqueueDec();
              continue;  
            }
            free(fileBuffer);
        }
        if(PopInbufferDec(index,bufferSize)!=AV_ERR_OK){
          signal_->errorNum_+=1;
        }else{ 
          decInCnt_++;  
        }
        timeStampDec_+=SAMPLE_DURATION_US;
        PopInqueueDec();
    }
}


void VCodecNdkSample::VDecOutLoopFunc(){
    while(true){
        if(!isVdecRunning_.load()){
            break;0
        }
    
    std::unique_lock<std::mutex> lock(signal_->vdecOutMutex_);
    signal_->vdecOutCond_.wait(lock,[this](){
       return signal_->outQueue_.size()>0; 
    });
    if(!isVdecRunning_.load()){
                break;
    }
    if(signal_->isVdecFlushing_.load()){
      PopOutqueueDec();
        continue;
    }
    unint32_t index=signal_->outQueue_.front();
    unint32_t flag=signal_->vdecOutFlagQueue_.front();
    if(flag==0){
      uint32_t ret;
      ret=OH_VideoDecoder_RenderOutputData(_codec,index);
      if(ret==0){
      }
      else{
        signal_->errorNum_+=1;
            break;
      }  
    }
    PopOutqueueDec();
    }
}

struct OH_AVCodec *VCodecNdkSample::CreateVideoDecoder(void){
    struct OH_AVCodec *codec=OH_VideoDecoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
    if(signal_==nullptr){
       signal_=new VDecSignal();
    }
    _callback.onError=OH_AVCodecOnErrorB;
    _callback.onNeedInputData=OH_AVCodecOnNeedInputDataB;
    _callback.onNeedOutputData=OH_AVCodecOnNeedOutputDataB;
    _callback.onStreamChanged=OH_AVCodecOnStreamChangedB;
    int32_t ret=OH_VideoDecoder_SetCallback(codec,_callback,static_cast<void *>(signal_));
    return codec;
}

struct OH_AVFormat *createFormat(){
    OH_AVFormat *DefaultFormat=OH_AVFormat_Create();
    OH_AVFormat_SetIntValue(DefaultFormat,OH_MD_KEY_WIDTH,DEFAULT_WIDTH);
    OH_AVFormat_SetIntValue(DefaultFormat,OH_MD_KEY_HEIGHT,DEFAULT_HEIGHT);
    OH_AVFormat_SetIntValue(DefaultFormat,OH_MD_KEY_PIXEL_FORMAT,DEFAULT_PIXELFORMAT);
    OH_AVFormat_SetIntValue(DefaultFormat,OH_MD_KEY_FRAME_RATE,DEFAULT_FRAMERATE);
    OH_AVFormat_SetIntValue(DefaultFormat,OH_MD_KEY_CODEC_MIME,DEFAULT_MIMETYPE_VIDEO_AVC);
    return DefaultFormat;
}

napi_value VCodecNdkSample::videoDec(napi_env env,napi_callback_info info){
    _codec=VCodecNdkSample::GetInstance()->CreateVideoDecoder();
    struct OH_AVFormat *format=createFormat();
    OH_VideoDecoder_Configure(_codec,format);
    OHNativeWindow *oHNativeWindow=(OHNativeWindow *) nativeWindow_;
    OH_VideoDecoder_SetSurface(_codec,oHNativeWindow);
    OH_VideoDecoder_Prepare(_codec);
    OH_VideoDecoder_SetParameter(_codec,format);
    if(testFile_==nullptr){
      testFile_=std::make_unique<std::ifstream>();
      testFile_->open("/data/storage/el2/base/haps/entry/cache/douyin.h264",std::ios::in|std::ios::binary);
    }
    if(readLoop_==nullptr){
       readLoop_=std::make_unique<std::thread>(&VCodecNdkSample::VDecInpLoopFunc,VCodecNdkSample::GetInstance());
    }
    if(vdecOutLoop_==nullptr){
      vdecOutLoop_=std::make_unique<std::thread>(&VCodecNdkSample::VDecInpLoopFunc,VCodecNdkSample::GetInstance());
    }
    isVdecRunning_.store(true);
    OH_VideoDecoder_Start(_codec);
    return nullptr;
}


void OnSurfaceCreatedCB(OH_NativeXComponent *component,void *window){
    nativeWindow_=window;
    int32_t ret;
    char idStr[OH_XCOMPONENT_ID_LEN_MAX+1]={};
    unint64_t idSize=OH_XCOMPONENT_ID_LEN_MAX+1;
    ret=OH_NativeXComponent_GetXComponentId(component,idStr,&idSize);
    if(ret!=OH_NATIVEXCOMPONET_RESULT_SUCCESS){
      return;
    }
}


void OnSurfaceChangedCB(OH_NativeXComponent *component,void *window){
    int32_t ret;
    char idStr[OH_XCOMPONENT_ID_LEN_MAX+1]={};
    unint64_t idSize=OH_XCOMPONENT_ID_LEN_MAX+1;
    ret=OH_NativeXComponent_GetXComponentId(component,idStr,&idSize);
    if(ret!=OH_NATIVEXCOMPONET_RESULT_SUCCESS){
      return;
    }    
}


void OnSurfaceDestroyedCB(OH_NativeXComponent *component,void *window){
    int32_t ret;
    char idStr[OH_XCOMPONENT_ID_LEN_MAX+1]={};
    unint64_t idSize=OH_XCOMPONENT_ID_LEN_MAX+1;
    ret=OH_NativeXComponent_GetXComponentId(component,idStr,&idSize);
    if(ret!=OH_NATIVEXCOMPONET_RESULT_SUCCESS){
      return;
    }    
}


void DispatchTouchEventCB(OH_NativeXComponent *component,void *window){
    int32_t ret;
    char idStr[OH_XCOMPONENT_ID_LEN_MAX+1]={};
    unint64_t idSize=OH_XCOMPONENT_ID_LEN_MAX+1;
    ret=OH_NativeXComponent_GetXComponentId(component,idStr,&idSize);
    if(ret!=OH_NATIVEXCOMPONET_RESULT_SUCCESS){
      return;
    }    
}


void onSurfaceListener(){
    auto renderCallBack=VCodecNdkSample::GetNXComponentCallback();
    renderCallback->OnSurfaceCreated=OnSurfaceCreatedCB;
    renderCallback->OnSurfaceChanged=OnSurfaceChangedCB;
    renderCallback->OnSurfaceDestroyed=OnSurfaceDestroyedCB;
    renderCallback->DispatchTouchEvent=DispatchTouchEventCB;
}

OH_NativeXComponent_Callback *VCodecNdkSample::GetNXComponentCallback(){
    return &VCodecNdkSample::callback_;
}


void VCodecNdkSample::OnSurfaceCreated(OH_NativeXComponent *component,void *window){
    
}

void VCodecNdkSample::OnSurfaceChanged(OH_NativeXComponent *component,void *window){
    
}

void VCodecNdkSample::OnSurfaceDestroyed(OH_NativeXComponent *component,void *window){
    
}

void VCodecNdkSample::DispatchTouchEvent(OH_NativeXComponent *component,void *window){
    
}

bool VCodecNdkSample::Export(napi_env env, napi_value exports) {
    LOGI("napi-->Export");
    napi_status status;
    napi_value exportInstance = nullptr;
    OH_NativeXComponent *nativeXComponent = nullptr;
    int32_t ret;
    char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {};
    uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;
    status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance);
    if (status != napi_ok) {
        return false;
    }
    status = napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent));
    if (status != napi_ok) {
        return false;
    }
    ret = OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize);
    if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
        return false;
    }
    SetNativeXComponent(nativeXComponent);
    OnSurfaceListener();
    LOGI("napi-->Export-->end");
    return true;
}


EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("_videoDec", VCodecNdkSample::videoDec),
    };
    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
    IJKPlayerNapi::GetInstance()->Export(env, exports);
    return exports;
}
EXTERN_C_END

static napi_module demoMoudule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "nativerender",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterModule(void) {
    napi_module_register(&ijkplayerModule);
}










